﻿/*
// CHANGE LOG
/modified June 22, 2007 - David Conner
/	version 2.01
/	restructured the loading methods to properly track loading progress
/
mod 16 jun 07 - ddb
ver 1.02
changed:

added:
toString() method
className_str:String
version_str:String

*/
// modified 06 OCT 2006 by ddb, sbm:
/*
old parsing would not build a single child node as array, so if you removed a node which changed the length to 1, the syntax module[o].text would then fail, as it would be parsed as modules.module.text (not array)
sometimes you want a node with a single childNode to be an object, like <intro><title>MyTitle</titel></intro>
- so you could refer to it as intro.title.text
sometimes you want a node with a single childNode to be an array, like <modules><module>MyTitle</module></modules>
- so you couuld refer to it as module[0].text
added new parsing behavior in createNode() function (in addition to old parsing -  backwards compatible) but allow a new way to always build as array
based on node naming convention:
any nodeName that contains the string "_array" will be built as an array, regardless of length of childNodes
* childNodes MUST still be named the same, as before
any nodeName that doesn't contain the string "_array" will be parsed according to the original rules above
*/
// modified 21 August 2006 by ddb, added PopUp.Alert error handling
// removed superfluous conditionals within onLoadHandler()...if (loadSuccess) branch
/*
// METHOD TABLE:
// XMLContainer methods
//
public function XMLContainer(file:String, callback:Function){
private function load (URL:String):Void {
private	function onProgress (alias:Object):Void {
private function createNode (node, parent, index):Void {
private function toString():Void{
*/
class com.utils.XMLContainer {
	private var className_str:String;
	private var version_str:String;
	// REQUIRED. path and file name to load; "xml/page1.xml"
	public var URL:String;
	// OPTIONAL. function name to call when load/parse is complete; "tempXMLarseHandler"
	private var loadHandler:Function;
	// container object for parsed xml tree;
	public var data:Object;
	// flag set to true when the xml has finished loading AND parsing and is ready for use
	public var complete:Boolean;
	// if the xml load fails, the error message will be contained here, else ""
	public var error:String;
	// percentage of xml load progress; 0-100
	public var progress:Number;
	private var progressInterval:Number;
	// internal container for xml
	private var tempXML:Object;
	// internal reference to this Object
	private var alias:Object;
	// internal var; use public wrapper method getBytesTotal() to access externally
	private var bytesTotal_num:Number;
	// internal var; use public wrapper method getBytesLoaded() to access externally
	private var bytesLoaded_num:Number;
	// constructor function
	public function XMLContainer(file:String, callback:Function) {
		className_str = "  XMLContainer";
		version_str = "v2.02";
		trace(this+" ("+version_str+")");
		alias = this;
		URL = file;
		loadHandler = callback;
		data = new Object();
		complete = false;
		error = "";
		tempXML = new XML();
		bytesTotal_num = -1;
		bytesLoaded_num = -1;
		load(URL);
	}
	private function load(URL:String):Void {
		var loadSuccess:Boolean;
		tempXML.alias = alias;
		tempXML.ignoreWhite = true;
		tempXML.onLoad = onLoadHandler;
		tempXML.sendAndLoad(URL, tempXML);
	}
	function onLoadHandler(success:Boolean):Void {
		//trace("XML Containter onLoadHandler: success = "+success);
		if (success) {
			alias.initLoad();
		} else {
			var temp_str:String = unescape(_level0._url);
			var temp_array:Array = temp_str.split(".swf");
			var localPath_str:String = temp_array[0].substring(0, (temp_array[0].lastIndexOf("/")));
			var error:String = "Error opening URL \""+localPath_str+"/"+URL+"\"";
			data = undefined;
		}
		if (error != null) {
			// show popup alert
			com.utils.PopUp.Alert({text:"<b>XML Status:</b>"+newline+error, root:_level0, textWidth:300});
		}
	}
	function initLoad() {
		//trace("initLoad()");
		bytesTotal_num = tempXML.getBytesTotal();
		bytesLoaded_num = tempXML.getBytesLoaded();
		clearInterval(progressInterval);
		progressInterval = setInterval(onProgress, 100, alias);
	}
	
	private function onProgress(alias:Object):Void {
		//trace("XMLContainer onProgress");
		var xmlBytesTotal:Number = alias.tempXML.getBytesTotal();
		var xmlBytesLoaded:Number = alias.tempXML.getBytesLoaded();
		var tempXMLPercentLoaded:Number = xmlBytesLoaded/xmlBytesTotal*100;
		progress = tempXMLPercentLoaded;
		alias.bytesLoaded_num = alias.tempXML.getBytesLoaded();
		//trace("XMLContainer tempXMLPercentLoaded = "+tempXMLPercentLoaded);
		//trace("XMLContainer bytesLoaded_num = "+alias.bytesLoaded_num);
		if (tempXMLPercentLoaded>=100) {
			alias.onLoadCompleteHandler();
		}
	}
	function onLoadCompleteHandler():Void {
		//trace("XMLContainer.onLoadCompleteHandler()");
		clearInterval(progressInterval);
		var root:XML;
		var children:XML;
		var parent:XML;
		// get the first real node
		var node:XMLNode = tempXML.firstChild;
		while (node.nodeType != 1) {
			node = node.nextSibling;
		}
		// create base object for parsed xml data
		var tempObject:Object = new Object();
		createNode(node, tempObject, undefined);
		// finished. return our results
		var error:String = null;
		this.data = tempObject;
		this.complete = true;
		this.progress = 100;
		//trace("firing onLoadComplete: this.complete = "+this.complete);
		delete tempXML;
		// trigger user defined callback function
		loadHandler();
	}
	public function getBytesTotal():Number {
		return bytesTotal_num;
	}
	public function getBytesLoaded():Number {
		//trace("XMLContainer.getBytesLoaded() returns "+bytesLoaded_num);
		return bytesLoaded_num;
	}
	private function createNode(node, parent, index):Void {
		// if it's an ELEMENT node...
		if (node.nodeType == 1) {
			// temp object for this record
			var tempObj:Object = {};
			//loop and add the attributes
			var att:XMLNode;
			tempObj.attributes = [];
			var i:Number = 0;
			for (att in node.attributes) {
				tempObj.attributes[att] = node.attributes[att];
				i++;
			}
			tempObj.attributes.length = i;
			//if there is only 1 child who's type is a text type 
			// new "_array" clause
			// if nodeName contains "_array", build node as array regardless of length of childNodes
			if (node.nodeName.indexOf("_array") != -1) {
				// build as array
				if (index != undefined) {
					parent[index] = tempObj;
				} else {
					parent[node.nodeName] = tempObj;
				}
				// loop through the nodes for that node
				for (var j = 0; j<node.childNodes.length; j++) {
					this.createNode(node.childNodes[j], tempObj, j);
				}
				tempObj.length = j;
			} else {
				// keep checking
				if (node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
					tempObj["text"] = unescape(node.firstChild.nodeValue.toString());
					if (index != undefined) {
						parent[index] = tempObj;
					} else {
						parent[node.nodeName] = tempObj;
					}
				} else {
					if (index != undefined) {
						parent[index] = tempObj;
					} else {
						parent[node.nodeName] = tempObj;
					}
					//this checks to see if there are multiple nodes with the same name
					//if so it then adds them as an array element under the parent
					var nodeNameCheck:String;
					var multipleNamesFound:Boolean = false;
					for (var j = 0; j<node.childNodes.length; j++) {
						if (nodeNameCheck == undefined) {
							nodeNameCheck = node.childNodes[j].nodeName;
						} else if (node.childNodes[j].nodeName == nodeNameCheck) {
							multipleNamesFound = true;
						}
					}
					// loop through the nodes for that node
					for (var j = 0; j<node.childNodes.length; j++) {
						//if the nodes contains multiple of the same nodeName
						if (multipleNamesFound) {
							this.createNode(node.childNodes[j], tempObj, j);
						} else {
							this.createNode(node.childNodes[j], tempObj, undefined);
						}
					}
					tempObj.length = j;
				}
			}
		}
	}
	private function toString():String {
		return className_str;
	}
}
